package com.gitee.wsl.data.state.repo

import com.gitee.wsl.data.state.KalugaState
import com.gitee.wsl.flow.event.SharedFlowCollectionEvent
import com.gitee.wsl.flow.event.onCollectionEvent
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

/**
 * A Cold [StateRepo], using a [MutableSharedFlow] as it's backing implementation.
 *
 * The repo will get initialized when the state is first read.
 *
 * There are then subsequent events for first collections, when there are no more collections, and later collections. Also see [onCollectionEvent].
 *
 * Be aware an initialization can take place if state is read, for example by [useState] or [takeAndChangeState], without actual collection events occurring.
 * However a SharedFlow without a replay buffer might trigger a collection to get the initial state if no explicit state is provided
 *
 * @param State the type of [KalugaState] represented by this repo.
 * @param F the type of [MutableSharedFlow] the state will be collected in.
 * @param context the [CoroutineContext] used to create a coroutine scope for this state machine. Make sure that if you pass a coroutine context that has sequential execution if you do not want simultaneous state changes. The default Main dispatcher meets these criteria.
 */
abstract class BaseColdStateRepo<State : KalugaState, F : MutableSharedFlow<State>>(
    context: CoroutineContext = Dispatchers.Main.immediate
) : StateRepo<State, F>(context) {

    private val initialized = atomic(false)

    abstract val lazyMutableFlow: Lazy<F>

    override val mutableFlow: F
        get() {
            val isInitialized = lazyMutableFlow.isInitialized()
            val flow = lazyMutableFlow.value
            if (!isInitialized && initialized.compareAndSet(expect = false, update = true)) {
                launch(coroutineContext) {
                    flow.onCollectionEvent { event ->
                        @Suppress("DEPRECATION")
                        when (event) {
                            SharedFlowCollectionEvent.NoMoreCollections -> noMoreCollections()
                            SharedFlowCollectionEvent.FirstCollection -> firstCollection()
                            SharedFlowCollectionEvent.LaterCollections -> laterCollections()
                        }
                    }
                }
            }
            return flow
        }

    abstract suspend fun firstCollection()

    abstract suspend fun laterCollections(): State

    abstract suspend fun noMoreCollections(): State
}